<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage core
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

interface FileSystem {

    /**
     * A file is not modified
     */
    const FILE_NOT_MODIFIED	= 0x00;

    /**
     * A file is added
     */
    const FILE_ADDED		= 0x01;

    /**
     * A file is removed
     */
    const FILE_REMOVED		= 0x02;

    /**
     * A file is modified
     */
    const FILE_MODIFIED		= 0x03;

    /**
     * Get the status of a file. A files status is defined as
     * {@link FileSystem :: FILE_NOT_MODIFIED} : the file was not modified or the file did not exist during previous checks and still does not exist
     * {@link FileSystem :: FILE_ADDED}        : the file was never checked before and has been added since
     * {@link FileSystem :: FILE_REMOVED}      : the file existed in a previous check, and has been removed since
     * {@link FileSystem :: FILE_MODIFIED}     : the file existed in a previous check, and has been modified since
     *
     * The check will be stored to the cache. If sinceExecution is set to false, the cache will
     * be ignored and an live status will be returned.
     *
     * @access public
     * @param string $filename The name of the file to check
     * @param boolean $sinceExecution Ignore the cache if set to true
     * @return int The file status (FILE_NOT_MODIFIED, FILE_ADDED, FILE_REMOVED or FILE_MODIFIED)
     */
    public function getFileStatus($filename, $sinceExecution = true);

    public function getFiles();

}

class DefaultFileSystem implements FileSystem {

    /**
     * The long term cache.
     *
     * @var Storage
     */
    private $storage;

    /**
     * The short term cache.
     *
     * @var array
     */
    private $cache;

    /**
     * Create the DefaultFileSystem with the corresponding cached storage.
     */
    public function __construct(StorageManager $storageManager) {
        $storage = $storageManager->getStorage('filesystem.files', true);
        $this->storage = $storageManager->cache($storage);

        $this->cache = array();
    }

    public function getFileStatus($filename, $sinceExecution = true) {
        $result = FileSystem :: FILE_NOT_MODIFIED;

        // If since execution, accept cache hits
        if($sinceExecution && array_key_exists($filename, $this->cache)) {
            $result = $this->cache[$filename];
            // If something about the file is already known ..
        } elseif(isset($this->storage[$filename])) {
            // .. and the file got deleted
            if(!file_exists($filename)) {
                $result = FileSystem :: FILE_REMOVED;
                // remove the file from the long term cache
                unset($this->storage[$filename]);
                // .. or the file might be modified
            } else {
                $recentChangedTime = filemtime($filename);
                $cachedChangedTime = (int)$this->storage[$filename];
                $result = ($recentChangedTime !== $cachedChangedTime) ? FileSystem :: FILE_MODIFIED : FileSystem :: FILE_NOT_MODIFIED;

                if($result === FileSystem :: FILE_MODIFIED) {
                    // store the update in the long term cache
                    $this->storage[$filename] = $recentChangedTime;
                }
            }
            // .. and store the result in the short term cache as well
            $this->cache[$filename] = $result;

            // If it is a completely new existing file (not monitored by the DefaultFileSystem)
        } elseif(file_exists($filename)) {
            $result = FileSystem :: FILE_ADDED;
            $recentChangedTime = filemtime($filename);
            $this->storage[$filename] = $recentChangedTime;
            $this->cache[$filename] = $result;
        }

        return $result;
    }

    public function getFiles() {
        return $this->storage->getOffsets();
    }
}

